#coding:utf-8

import math
import os
import sys

from typing import Callable
from PySide6.QtGui import QWheelEvent,QResizeEvent
from  PySide6.QtWidgets import QWidget,QHBoxLayout,QVBoxLayout,QLabel,QTextEdit,QGridLayout,QStackedWidget,QApplication
from PySide6.QtCore import Qt,QThread,Slot,Signal,QSettings
from qfluentwidgets import TeachingTip,InfoBarIcon,TeachingTipTailPosition
from qfluentwidgets import NavigationInterface,LineEdit,PrimaryPushButton,SingleDirectionScrollArea,NavigationItemPosition
from qframelesswindow import FramelessWindow, StandardTitleBar
from qfluentwidgets import FluentIcon as FIF
from queue import Queue,Empty
from config import builder
from datetime import datetime


da = ""
active = []
Agents =dict()
saveHistory =False
isMulti = False
useALL  = False
# class MyQMessage(QtWidgets.)

from qfluentwidgets import NavigationWidget

class MyThead(QThread):
    def __init__(self,parent):
        super().__init__(parent)
        self.queue =Queue(10)
        self.exist = False
    def addtask(self,fn):
        self.queue.put(fn)
    def stop(self):
        self.exist =True
        self.queue.put(lambda: print("hello") )
    def run(self):
        while not self.exist:
            try:
                fn =self.queue.get(timeout=1)
                if fn is None:
                    break
                if self.exist:
                    break
                try:
                    fn()
                except Exception as e:
                    print(f"{e=}")
                    break
            except Empty:
                if self.exist:
                    break
                continue
        print("线程结束")

class Flow(QWidget):
    def __init__(self):
        super().__init__()
        self.layout_ = QVBoxLayout(self)
        self.layout_.setAlignment(Qt.AlignmentFlag.AlignTop)
        self.setLayout(self.layout_)
    def addWidget(self,w:QWidget):
        self.layout_.addWidget(w)
    
    
class TextArea(QTextEdit):
    def __init__(self,parent):
        super().__init__()
        self.setReadOnly(True)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.m_txtDefaultHeight = 50
        self.setMaximumHeight(50)
        self.setMinimumHeight(50)
        self.top,self.bottom = self.contentsMargins().top(), self.contentsMargins().bottom()
        self.ctx = ""
        self.pa = parent
        self.ud = f"<!-- TS:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} -->"
        self.ds = f"{datetime.now().strftime('%Y_%m_%d')}"
        self.saved = False
        self.callback :Callable[[float],None] 
        self.lastH = -1.0
    def insertMarkdown(self,s:str):
        self.ctx +=s
        self.setMarkdown(self.ctx)
        self.checkResize()
    def regist(self,callback:Callable[[float],None] ):
        self.callback = callback
    @Slot()
    def checkResize(self):
        # print("更新变动")
        h = self.document().size().height()
        if h>self.lastH:
            self.lastH = h
            self.callback(h+self.top+self.bottom)
        if h>self.m_txtDefaultHeight:
            self.m_txtDefaultHeight = math.floor(h)
            
            self.setMaximumHeight(math.floor(h))
            self.setMaximumHeight(math.floor(h))
            rect = self.geometry()
            self.setGeometry(rect.x(),rect.y(),rect.width(),math.floor(h)+self.top+self.bottom)
    @Slot()
    def export2File(self):

        if self.saved:
            return
        with open(f"./chart_{self.ds}.md",mode="a+",encoding="utf8") as fw:
            size = self.ctx.split("-----",maxsplit=1)
            if len(size) >1 and len(size[1])>0:
                fw.write(self.ctx)
                fw.write(f"\n{self.ud}\n")
                self.saved =True

    def wheelEvent(self,e :QWheelEvent):
        self.pa.wheelEvent(e)
    def resizeEvent(self, e: QResizeEvent) -> None:
        self.document().setTextWidth(self.viewport().width())
        margins = self.contentsMargins()
        height = int(self.document().size().height() + margins.top() + margins.bottom())
        self.setFixedHeight(height)
        self.setMaximumHeight(height)
        self.setMaximumHeight(height)
        
from qfluentwidgets import NavigationWidget
class agentBar(NavigationWidget):
    def __init__(self, isSelectable: bool, da,parent=None,):
        super().__init__(isSelectable, parent)
        self.NAME = QLabel()
        self.NAME.setText(da)
        self.NAME.setStyleSheet("font-size:8px")
        hbox =QHBoxLayout(self)
        hbox.addWidget(self.NAME)
        self.setLayout(hbox)
    def changeAgent(self,da:str):
        if da!=self.NAME.text():
            self.NAME.setText(da)    

class MyApp(QWidget):
    sig = Signal(str)
    def __init__(self, *args,**kwargs) -> None:
        super().__init__(*args,**kwargs)
        self.setWindowTitle("PYSIDE6 demo")
        self.id =0
        self.gid =0
        self.resize(800,450)
        self.m_end =0
        self.set_ui()
        self.ctx =""

    def set_ui(self):
        self.scrollarea = SingleDirectionScrollArea()
        self.scrollarea.setWidgetResizable(True)
        self.scrollbar = self.scrollarea.verticalScrollBar()
        self.flow = Flow()
        self.create_txt()
        self.flow.addWidget(self.txt)
        self.flow.setMinimumHeight(self.scrollarea.size().height())
        self.scrollarea.setWidget(self.flow)
        self.button = PrimaryPushButton("发送")
        self.inputtext = LineEdit()
        self.inputtext.setPlaceholderText("请输入你的想法")
        self.laout = QGridLayout(self)
        # self.appendWS([self.text,self.button,self.inputtext])
        self.laout.addWidget(self.scrollarea,0,0,3,4)
        self.laout.addWidget(self.inputtext,4,0,1,3)
        self.laout.addWidget(self.button,4,3,1,1)
        self.button.clicked.connect(self.magic)
        self.agent = None
        self.worker = MyThead(self)
        self.setStyleSheet("QPushButton{border-width:1px;border-radius:5px;background-color: #3366FF ;}\
                           QLabel{border:3px solid black ;border-radius:5px;color:black}")
    def changeAgent(self,s):
        if s in Agents:
            if s==da:
                return
            
            self.agent = Agents[s]
    def changeScrollBar(self, f:float):
            if self.m_end +f >= self.flow.minimumHeight():
                self.flow.setMinimumHeight(int(self.m_end+f)+10)
                self.scrollbar.setValue(self.scrollbar.maximum())
    def create_txt(self):
        if self.gid>1:
            self.m_end =self.txt.geometry().y()+self.txt.geometry().height()+self.txt.top+self.txt.bottom
            self.sig.disconnect(self.txt.insertMarkdown)
        self.txt = TextArea(self)
        self.scrollbar.setValue(self.scrollbar.maximum())
        self.txt.regist(self.changeScrollBar)
        self.flow.addWidget(self.txt)
        self.sig.connect(self.txt.insertMarkdown)

    def appendWS(self,l:list[QWidget]):
        for it in l:
            self.layout.addWidget(it)

    def loopread(self,s:str):
        if useALL:
            for k,a in Agents.items():
                try:
                    self.txt.insertMarkdown(f"=={k}==    \n")
                    a.StreamCall(s,self.append)
                except Exception as e:
                    self.showMessage("Error",f"f{e=} found in all agent mode executed")
        else:
            self.agent.StreamCall(s,self.append) # type: ignore
        self.button.setEnabled(True)
        if saveHistory:
            self.txt.export2File()

    def append(self,s):
        self.sig.emit(s)
    def showMessage(self,ty:str,s:str):
        TeachingTip.create(
                target=self.button, # type: ignore
                icon=InfoBarIcon.ERROR,
                title=ty,
                content=s,
                isClosable=True,
                tailPosition=TeachingTipTailPosition.BOTTOM_RIGHT,
                duration=2000,
                parent=self
            )
        logging.debug(f"type={ty},{s}")

    @Slot()
    def magic(self):
        if self.agent is None:
            if len(Agents)==0 or da == "":
                self.showMessage("Error:","请先在设置页面配置一个chatgpt api的接口设置")
            else:
                self.agent = Agents[da]
        self.gid +=1
        # self.tts.say("你好")
        if self.gid>1:
           self.create_txt()
        #    self.flow.addWidget(self.txt)
        if self.inputtext.text()!="":
            self.button.setDisabled(True)
            self.txt.insertMarkdown(f"#### Q: {self.inputtext.text()}\n-----\n**A:**  \n")
            if not self.worker.isRunning():
                self.worker.start()
            
            self.worker.addtask(lambda : self.loopread(self.inputtext.text()))
            
    def close(self) -> bool:
        self.worker.stop()
        self.worker.wait()
        self.worker.quit()
        self.worker.terminate()
        return super().close()
    def closeEvent(self, event) -> None:
        self.worker.stop()
        self.worker.wait()
        self.worker.quit()
        self.worker.terminate()
        event.accept()
        return super().closeEvent(event)

from config import ConfigView
class MyWin(FramelessWindow):
    def __init__(self) -> None:
        super().__init__()
        self.resize(800,450)
        self.reloadConfig()
        self.setTitleBar(StandardTitleBar(self))
        self.ca = MyApp()
        self.history =MyApp()
        self.config = ConfigView(self,Agents,da)
        self.config.card.currentTextChanged.connect(self.ca.changeAgent)
        self.hBoxLayout = QHBoxLayout(self)
        self.hBoxLayout.setSpacing(0)
        self.hBoxLayout.setContentsMargins(0, self.titleBar.height(), 0, 0)
        self.stackWidget = QStackedWidget(self)
        self.stackWidget.addWidget(self.ca)
        self.stackWidget.addWidget(self.config)
        # self.stackWidget.addWidget(self.history)
        self.lda =agentBar(False,da,self)
        self.navigationInterface = NavigationInterface(self, showMenuButton=False)
        self.navigationInterface.setMinimumWidth(100)
        self.navigationInterface.addItem("会话",FIF.CHAT,"chat",onClick=lambda: self.switchTo(self.ca),position=NavigationItemPosition.TOP,tooltip="会话")
        self.navigationInterface.addItem("设置",FIF.SETTING,"setting",onClick=lambda: self.switchTo(self.config),position=NavigationItemPosition.TOP,tooltip="设置")
        self.navigationInterface.addWidget("模型",self.lda,onClick=None,position=NavigationItemPosition.BOTTOM,tooltip="模型")
        self.config.card.currentTextChanged.connect(self.lda.changeAgent)
        self.hBoxLayout.setSpacing(0)
        self.hBoxLayout.setContentsMargins(0, self.titleBar.height(), 0, 0)
        self.hBoxLayout.addWidget(self.navigationInterface)
        self.hBoxLayout.addWidget(self.stackWidget)
        self.hBoxLayout.setStretchFactor(self.stackWidget, 1)
    # 主动处理子控件的资源回收，仅重写子控件close 不行
    def closeEvent(self, event) -> None:
        self.ca.closeEvent(event)
        return super().closeEvent(event)
    def switchTo(self, widget):
        self.stackWidget.setCurrentWidget(widget)
    def reloadConfig(self):

        global da,Agents
        wkd = "./config"
        logging.debug(f"{os.path.abspath(wkd)}")
        if not os.path.exists(wkd):
            os.makedirs(wkd)
        if os.path.exists(os.path.join(wkd,"config.ini")):
            seting = QSettings(os.path.join(wkd,"config.ini"),QSettings.Format.IniFormat)
            isUseALL = seting.value("global/useAll",type=bool)
            # 实验性功能
            if isUseALL:
                global useALL
                useALL = True
            Agents,temp = builder(wkd)
            if temp is None:
                if len(Agents)==0:
                    return 
                tlist = [ x for x in Agents]
                tlist.sort()
                da = tlist[0]
            else:
                da = temp # type: ignore


import traceback
import logging
 
logging.basicConfig(filename='log.txt', level=logging.DEBUG,
     format='%(asctime)s - %(levelname)s - %(message)s')
 
if __name__=="__main__":
    try:
        app =QApplication()
        widget = MyWin()
        widget.show()
        app.exec()
        sys.exit(0)
    except Exception as e:
 #方案一，自己定义一个文件，自己把错误堆栈信息写入文件。
 #errorFile = open('log.txt', 'a')
 #errorFile.write(traceback.format_exc())
 #errorFile.close()
 
 #方案二，使用Python标准日志管理维护工具。
        logging.debug(f"{traceback.format_exc()}, {e=}")
